/*
 * aurora - Communications with Magnetek Aurora Inverter
 *
 * Copyright (C) 2006-2010 Curtis J. Blank curt@curtronics.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program may be used and hosted free of charge by anyone for
 * personal purposes as long as this and all included copyright
 * notice(s) remain intact.
 *
 * Obtain permission before selling the code for this program or
 * hosting this software on a commercial website, excluding if it is
 * solely hosted for distribution, that is allowable . In all cases
 * copyright must remain intact.
 *
 * This work based on Magnetek's 'Aurora PV Inverter - Communications
 * Protocol -' document, Rel. 1.8 09/05/2005
 * Staring with v1.5-0 this work based on Power One's 'Aurora Inverter 
 * Series - Communication Protocol -' document, Rel. 4.6 25/02/09
 *
 * Special thanks to Tron Melzl at Magnetek for all his help including,
 * but not limited to, supplying the Communications Protocol document
 *
 * modified 17-jul-2006 cjb	1. Last 7 Days production value has been dropped in the v2.3 Communications Protocol doc
 * modified 13-oct-2006 cjb	1. correct possible divide by zero when calculating inverter efficiency
 * modified 25-apr-2007 cjb	1. update set time warning
 *				2. take into account Daylight Savings Time when setting the Aurora's time
 * modified 29-dec-2008 cjb     1. correct an error in strftime that only may show up in the first or last
 *                                 week of the year (%G vs %Y)
 * modified 19-aug-2009 cjb	1. szSerBuffer needs to be [11] if ending with "\0"
 * modified 18-sep-2009 cjb	1. add cCommandEnd = '\0'
 * modified 12-oct-2009 cjb	1. add -o option to output data to a file
 * modified 30-oct-2009 cjb     1. added errno for read problems
 * modified 07-mar-2010 cjb	1. fix sizeof pointer passing in memset
 *				2. use -Y option in Communicate function
 *				3. in ReadNextChar restore serial port settings and clear lock if exiting
 * modified 13-mar-2010 cjb	1. if yReadPause is set use it
 * modified 28-mar-2010 cjb	1. working on adding more DSP information
 * modified 27-jul-2010 cjb	1. added -P option to throttle commands sent to the inverter
 * modified 21-sep-2010 cjb	1. fix using wrong param when displaying TransState message
 *				2. pass TransState the command description so if it gets a non-zero status it can
 *				   report for what command it got it
 *				3. added reporting for "Last four alarms"
 *
 */

char     VersionC[6] = "1.6.7";

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>
#include <math.h>
#include <string.h>
#include <getopt.h>
#include <time.h>
#include <errno.h>
#include <error.h>
#include <mysql/mysql.h> 
#include "include/main.h"
#include "include/comm.h"
#include "include/names.h"
#include "include/states.h"
#include "include/mysql.h"

BOOL bVerbose;
BOOL bColumns;
BOOL bGetInvTime;
BOOL bGetLocTime;
BOOL bGetDSP;
BOOL bGetDSPExtended;
BOOL bGetDSP3Phase;
BOOL bHideDSP;
BOOL bGetEnergy;
BOOL bCommCheck;
BOOL bColOutput;
BOOL bCalcGridPwr;
char RunTime[18];
float yCost;

char VersionSHc[6] = VersionSH;

static char szSerBuffer[_szSerBufferLen];			/* serial read/write buffer */
static WORD crcValue;

/* local functions */
static int TransState(int TransCode, char Desription[]);
static int PrintModel(int ModelID);
static long GetCEdata(int fdser, int yAddress, int opcode, int param, char desription[]);
static float GetDSPdata(int fdser, int yAddress, int opcode, int param, char desription[]);
static int GetCountersData(int fdser, int yAddress, int param, char *uptime);
static int Communicate(int fdser, int yAddress);
static int ReadNextChar(int nfd, char *pChar, int timeout);
static int ReadToBuffer(int nfd, char *pszBuffer, int nBufSize);
static void Delay(int secs, long microsecs);
static WORD crc16(char *data_p, unsigned short length);
static char* FindString(WORD wRule, char *ptr);
static float *cvrtFloat(char *Buffer);
static unsigned long cvrtLong(char *Buffer);
static unsigned long cvrtShort(char *Buffer);


/*--------------------------------------------------------------------------
    TransState
----------------------------------------------------------------------------*/
int TransState(int TransCode, char Desription[])
{
    if(bVerbose) fprintf(stderr, "Transmission State Check: %i\n",TransCode);
    if (TransCode == 0) return(1);
    fprintf(outfp, "\nTransmission State: %2i Command: \"%s\" %s\n\n",TransCode,Desription,FindString(TransCode, szTransStates));
    return(0);
}


/*--------------------------------------------------------------------------
    CommCheck
----------------------------------------------------------------------------*/
int CommCheck(int fdser, int yAddress)
{
    int nCnt;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetVer;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetVer)) {
        ModelID = szSerBuffer[aPar1];
        if(bVerbose) {
            fprintf(stderr, "Model ID \"%c\" ",ModelID);
            PrintModel(ModelID);
        }
        return(0);
    }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetState
----------------------------------------------------------------------------*/
int GetState(int fdser, int yAddress)
{
    int nCnt;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetState;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetState)) {
        fprintf(outfp, "\nGlobal State:          %s\n",FindString((int)szSerBuffer[aMState], szGlobalStates));
        fprintf(outfp, "Inverter State:        %s\n",FindString((int)szSerBuffer[aParam1], szInverterState));
        fprintf(outfp, "Channel 1 Dc/Dc State: %s\n",FindString((int)szSerBuffer[aParam2], szDcDcStatus));
        fprintf(outfp, "Channel 2 Dc/Dc State: %s\n",FindString((int)szSerBuffer[aParam3], szDcDcStatus));
        fprintf(outfp, "Alarm State:           %s\n",FindString((int)szSerBuffer[aParam4], szAlarmState));
        return(0);
    }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetLastAlarms
----------------------------------------------------------------------------*/
int GetLastAlarms(int fdser, int yAddress)
{
    int nCnt;

    strcpy(szSerBuffer, _clearCMD);             /* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;           /* set inverter address */
    szSerBuffer[cCommand] = opGetLastAlarms;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetLastAlarms)) {
        fprintf(outfp, "\nAlarm 1:               %s\n",FindString((int)szSerBuffer[aParam1], szAlarmState));
        fprintf(outfp, "Alarm 2:               %s\n",FindString((int)szSerBuffer[aParam2], szAlarmState));
        fprintf(outfp, "Alarm 3:               %s\n",FindString((int)szSerBuffer[aParam3], szAlarmState));
        fprintf(outfp, "Alarm 4:               %s\n",FindString((int)szSerBuffer[aParam4], szAlarmState));
        return(0);
    }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetPN
----------------------------------------------------------------------------*/
int GetPN(int fdser, int yAddress)
{
    int nCnt;
    char PartNumber[6];

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetPN;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0) {
       strncpy(PartNumber, szSerBuffer, 6);
       fprintf(outfp, "\nPart Number: %s\n",PartNumber);
       return(0);
   }
    return(-1);
}


/*--------------------------------------------------------------------------
    PrintModel
----------------------------------------------------------------------------*/
int PrintModel(int ModelID)
{
    if (outfp != stderr) fprintf(outfp, "\nInverter Version:");
    fprintf(outfp, "  -- ");
    switch (ModelID) {
        case 'i':       fprintf(outfp, "%s -- ",aPar1i); break;
        case 'o':       fprintf(outfp, "%s -- ",aPar1o); break;
        case 'I':       fprintf(outfp, "%s -- ",aPar1I); break;
        case 'O':       fprintf(outfp, "%s -- ",aPar1O); break;
        case '5':       fprintf(outfp, "%s -- ",aPar15); break;
        case '6':       fprintf(outfp, "%s -- ",aPar16); break;
        case 'P':       fprintf(outfp, "%s -- ",aPar1P); break;
        case 'C':       fprintf(outfp, "%s -- ",aPar1C); break;
        case '4':       fprintf(outfp, "%s -- ",aPar14); break;
        case '3':       fprintf(outfp, "%s -- ",aPar13); break;
        case '2':       fprintf(outfp, "%s -- ",aPar12); break;
        case '1':       fprintf(outfp, "%s -- ",aPar11); break;
        case 'D':       fprintf(outfp, "%s -- ",aPar1D); break;
        case 'X':       fprintf(outfp, "%s -- ",aPar1X); break;
        default:        fprintf(outfp, "%s -- ","unknown"); break;
    }
    fprintf(outfp, "\n");
    return(0);
}


/*--------------------------------------------------------------------------
    GetVer
----------------------------------------------------------------------------*/
int GetVer(int fdser, int yAddress)
{
    int nCnt;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetVer;
    szSerBuffer[cCommandEnd] = '\0';
    szSerBuffer[cParam1] = '.';
    nCnt = Communicate(fdser, yAddress);
    crcValue = crc16(szSerBuffer, 8);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetVer)) {
        PrintModel(szSerBuffer[aPar1]);
        switch (szSerBuffer[aPar2]) {
            case 'A':       fprintf(outfp, "%s -- ",aPar2A); break;
            case 'E':       fprintf(outfp, "%s -- ",aPar2E); break;
            case 'S':       fprintf(outfp, "%s -- ",aPar2S); break;
            case 'I':       fprintf(outfp, "%s -- ",aPar2I); break;
            case 'U':       fprintf(outfp, "%s -- ",aPar2U); break;
            case 'K':       fprintf(outfp, "%s -- ",aPar2K); break;
            case 'F':       fprintf(outfp, "%s -- ",aPar2F); break;
            default:        fprintf(outfp, "%s -- ","unknown"); break;
        }
        switch (szSerBuffer[aPar3]) {
            case 'T':       fprintf(outfp, "%s -- ",aPar3T); break;
            case 'N':       fprintf(outfp, "%s -- ",aPar3N); break;
            default:        fprintf(outfp, "%s -- ","unknown"); break;
        }
        switch (szSerBuffer[aPar4]) {
            case 'W':       fprintf(outfp, "%s -- ",aPar4W); break;
            case 'N':       fprintf(outfp, "%s -- ",aPar4N); break;
            default:        fprintf(outfp, "%s -- ","unknown"); break;
        }
        fprintf(outfp, "\n");
        return(0);
    }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetConf
----------------------------------------------------------------------------*/
int GetConf(int fdser, int yAddress)
{
    int nCnt;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetConfig;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    crcValue = crc16(szSerBuffer, 8);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetConfig)) {
        switch (szSerBuffer[aConfCode]) {
            case ConfCode0:	fprintf(outfp, "\n%s\n",_ConfCode0); break;
            case ConfCode1:	fprintf(outfp, "\n%s\n",_ConfCode1); break;
            case ConfCode2:	fprintf(outfp, "\n%s\n",_ConfCode2); break;
            default:        break;
        }
        return(0);
    }
    return(-1);
}



/*--------------------------------------------------------------------------
    GetSN
----------------------------------------------------------------------------*/
int GetSN(int fdser, int yAddress)
{
    int nCnt;
    char SerialNumber[6];

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetSN;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0) {
        strncpy(SerialNumber, szSerBuffer, 6);
        fprintf(outfp, "\nSerial Number: %s\n",SerialNumber);
        return(0);
   }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetVerFW
----------------------------------------------------------------------------*/
int GetVerFW(int fdser, int yAddress)
{
    int nCnt;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetVerFW;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetVerFW)) {
        fprintf(outfp, "\nFirmware: %c.%c.%c.%c\n",szSerBuffer[aRel3],szSerBuffer[aRel2],szSerBuffer[aRel1],szSerBuffer[aRel0]);
        return(0);
   }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetMfgDate
----------------------------------------------------------------------------*/
int GetMfgDate(int fdser, int yAddress)
{
    int nCnt;
    char MfgWeek[3] = "  \0";
    char MfgYear[3] = "  \0";

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetMfg;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetMfg)) {
        MfgWeek[0] = szSerBuffer[aWeekH];
        MfgWeek[1] = szSerBuffer[aWeekL];
        MfgYear[0] = szSerBuffer[aYearH];
        MfgYear[1] = szSerBuffer[aYearL];

        fprintf(outfp, "\nManufacturing Date: Year %s Week %s\n",MfgYear,MfgWeek);
        return(0);
   }
    return(-1);
}

/*--------------------------------------------------------------------------
GetMDE
Added 2011-03-09 Bret Watson
-------------------------------------------------------------------------*/
int GetMDE(int fdser, int yAddress)
{
    MYSQL *mysql;
    long DAILY;
    float GVR, GPR, GCR;
    char query[512];
    const char *host = "127.0.0.1";
    const char *user = "aurora";
    const char *password = "warming";
    const char *database = "local_data";
    unsigned int port = 3306;
    const char *socket = NULL;
    unsigned long flag = 0;
    unsigned long bytes;
    
    
    
    /* init mysql and connect to teh database -*/
    mysql = mysql_init(NULL);
    mysql_real_connect(mysql,host,user,password,database,port,socket,flag);
    
    /*----*/
    /* DAILY is Total Energy today
    GVR is Grid Voltage reading
    GPR is Exported Power to Grid
    */
    
    if((DAILY = GetCEdata(fdser,yAddress,opGetCE,CEpar0,_opGetCE)) < 0)
    {
        fprintf(stderr, "\nDAILY = %d",(int)DAILY);
        return(-1);
    }
    
    if(( GVR = GetDSPdata(fdser,yAddress,opGetDSP,ToM1,_opGetDSP)) < 0)
    {
        fprintf(stderr, "\nGVR = %12.6f",GVR);
        return(-1);
    }
    
    /*if(( GPR = GetDSPdata(fdser,yAddress,opGetDSP,ToM3,_opGetDSP)) < 0)
    {
        fprintf(stderr, "\nGPR = %12.6f",GPR);
        return(-1);
    }   */
    if((GCR = GetDSPdata(fdser,yAddress,opGetDSP,ToM2,_opGetDSP)) < 0) return(-1);
    GPR = GVR * GCR;
    
    /*--- build sql insert ---*/  
    sprintf(query,"insert into DayData (DateTime,Inverter,Serial,CurrentPower,EtotalToday,CurrentGVolts) values (NOW(),'PVI-2000', '120744',%12.6f,%d,%12.6f)",GPR,(int)DAILY,GVR);
    if(bVerbose) fprintf(stderr, "\nQuery = %s ",query);
    
    bytes = strlen(query);
    mysql_real_query(mysql, query, bytes);
    
    if(bVerbose) fprintf(stderr, "\ndone query ");
    
    mysql_close(mysql);
    if(bVerbose) fprintf(stderr, "\nclosed database ");
    
    return(0);
}
/*--------------------------------------------------------------------------
    GetCE
----------------------------------------------------------------------------*/
int GetCE(int fdser, int yAddress)
{
    long DAILY, WEEKLY, LAST7DAYS, MONTHLY, YEARLY, TOTAL, PARTIAL;

    if(bVerbose) fprintf(stderr, "\nAttempting to get Partial Energy value ");
    if((PARTIAL = GetCEdata(fdser,yAddress,opGetCE,CEpar6,_opGetCE)) < 0) return(-1);
    if((DAILY = GetCEdata(fdser,yAddress,opGetCE,CEpar0,_opGetCE)) < 0) return(-1);
    if((WEEKLY = GetCEdata(fdser,yAddress,opGetCE,CEpar1,_opGetCE)) < 0) return(-1);
//    if((LAST7DAYS = GetCEdata(fdser,yAddress,opGetCE,CEpar2,_opGetCE)) < 0) return(-1);
    LAST7DAYS = 0.0;	/* do this for now since this has been dropped in the v2.3 Communications Protocol doc	*/
			/* placeholder for the -c option for now						*/
    if((MONTHLY = GetCEdata(fdser,yAddress,opGetCE,CEpar3,_opGetCE)) < 0) return(-1);
    if((YEARLY = GetCEdata(fdser,yAddress,opGetCE,CEpar4,_opGetCE)) < 0) return(-1);
    if((TOTAL = GetCEdata(fdser,yAddress,opGetCE,CEpar5,_opGetCE)) < 0) return(-1);

    if(bColumns) {
        fprintf(outfp, "%11.3f  %11.3f  %11.3f  %11.3f  %11.3f  %11.3f  %11.3f  ",DAILY/1000.0,WEEKLY/1000.0,LAST7DAYS/1000.0,MONTHLY/1000.0,YEARLY/1000.0,TOTAL/1000.0,PARTIAL/1000.0);
        bColOutput = TRUE;
    }
    else
    {
        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar0,DAILY/1000.0);
        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(DAILY/1000.0)*yCost);

        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar1,WEEKLY/1000.0);
        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(WEEKLY/1000.0)*yCost);

//        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar2,LAST7DAYS/1000.0);	/* see above note */
//        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(LAST7DAYS/1000.0)*yCost);

        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar3,MONTHLY/1000.0);
        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(MONTHLY/1000.0)*yCost);

        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar4,YEARLY/1000.0);
        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(YEARLY/1000.0)*yCost);

        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar5,TOTAL/1000.0);
        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(TOTAL/1000.0)*yCost);

        fprintf(outfp, "\n%-26s = %11.3f KWh",_CEpar6,PARTIAL/1000.0);
        if(yCost > 0) fprintf(outfp, "\t($%8.3f)",(PARTIAL/1000.0)*yCost);

        fprintf(outfp, "\n");
    }

    return(0);
}


/*--------------------------------------------------------------------------
    GetCEdata
----------------------------------------------------------------------------*/
long GetCEdata(int fdser, int yAddress, int opcode, int param, char desription[])
{
    int nCnt;
    unsigned long paramValLong;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opcode;		/* set Measure request to the Energy opcode */
    szSerBuffer[cParam1] = param;
    szSerBuffer[cCommandEnd+1] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],desription)) {
        paramValLong = cvrtLong(szSerBuffer);
        if(bVerbose) fprintf(stderr, "value        %12lu\n",(unsigned long)paramValLong);
    }
    else
        return(-1);

    return((int)paramValLong);
}


/*--------------------------------------------------------------------------
    GetDSP
----------------------------------------------------------------------------*/
int GetDSP(int fdser, int yAddress)
{
    float GVR, GCR, GPR, GPRC=0.0, FRQ, INVeff=0.0, INVeffC=0.0, INVtemp, ENVtemp, STR1V, STR1C, STR2V, STR2C, PVpwr;

    if((FRQ = GetDSPdata(fdser,yAddress,opGetDSP,ToM4,_opGetDSP)) < 0) return(-1);
    if((GVR = GetDSPdata(fdser,yAddress,opGetDSP,ToM1,_opGetDSP)) < 0) return(-1);
    if((GCR = GetDSPdata(fdser,yAddress,opGetDSP,ToM2,_opGetDSP)) < 0) return(-1);
    if((GPR = GetDSPdata(fdser,yAddress,opGetDSP,ToM3,_opGetDSP)) < 0) return(-1);

    if((STR1V = GetDSPdata(fdser,yAddress,opGetDSP,ToM23,_opGetDSP)) < 0) return(-1);
    if((STR1C = GetDSPdata(fdser,yAddress,opGetDSP,ToM25,_opGetDSP)) < 0) return(-1);
    if((STR2V = GetDSPdata(fdser,yAddress,opGetDSP,ToM26,_opGetDSP)) < 0) return(-1);
    if((STR2C = GetDSPdata(fdser,yAddress,opGetDSP,ToM27,_opGetDSP)) < 0) return(-1);

    if((INVtemp = GetDSPdata(fdser,yAddress,opGetDSP,ToM21,_opGetDSP)) < 0) return(-1);
    if((ENVtemp = GetDSPdata(fdser,yAddress,opGetDSP,ToM22,_opGetDSP)) < 0) return(-1);

    if(bCalcGridPwr) GPRC = GVR * GCR;

    PVpwr = (STR1V*STR1C)+(STR2V*STR2C);
    if(PVpwr > 0) {
        INVeff = (GPR/PVpwr)*100;
        INVeffC = (GPRC/PVpwr)*100;
    }

    if(bColumns) {
        fprintf(outfp, "%d  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  ",(int)time(NULL),STR1V,STR1C,STR1V*STR1C,STR2V,STR2C,STR2V*STR2C,GVR,GCR,GPR,FRQ,INVeff,INVtemp,ENVtemp);
        bColOutput = TRUE;
    }
    else
    {
        fprintf(outfp, "\n%-26s = %11.6f V\n",_ToM23,STR1V);
        fprintf(outfp, "%-26s = %11.6f A\n",_ToM25,STR1C);
        fprintf(outfp, "%-26s = %11.6f W\n",_Str1P,STR1V*STR1C);

        fprintf(outfp, "\n%-26s = %11.6f V\n",_ToM26,STR2V);
        fprintf(outfp, "%-26s = %11.6f A\n",_ToM27,STR2C);
        fprintf(outfp, "%-26s = %11.6f W\n",_Str2P,STR2V*STR2C);

        fprintf(outfp, "\n%-26s = %11.6f V\n",_ToM1,GVR);
        fprintf(outfp, "%-26s = %11.6f A\n",_ToM2,GCR);
        fprintf(outfp, "%-26s = %11.6f W\n",_ToM3,GPR);
        if(bCalcGridPwr) fprintf(outfp, "%-26s = %11.6f W\n",_ToM3C,GPRC);
        fprintf(outfp, "%-26s = %11.6f Hz.\n",_ToM4,FRQ);

        fprintf(outfp, "\n%-26s = %11.1f %s",_DcAcEff,INVeff,"%");
        if(bCalcGridPwr) fprintf(outfp, " (Using Grid Power Reading)\n%-26s = %11.1f %s (Using Grid Power Calculated)",_DcAcEff,INVeffC,"%");
        fprintf(outfp, "\n%-26s = %11.6f C\n",_ToM21,INVtemp);
        fprintf(outfp, "%-26s = %11.6f C\n",_ToM22,ENVtemp);
    }

    return(0);
}

void PrintBuffer()
{
    int i;
     for (i = 0; i < cSIZE; i++) {
        fprintf(stderr, "%02x ",(unsigned char)szSerBuffer[i]);
    }
    fprintf(stderr, "\n");
}

/*--------------------------------------------------------------------------
    GetDSPExtended
----------------------------------------------------------------------------*/
int GetDSPExtended(int fdser, int yAddress)
{
    float VB,ILD,ILI,P1,P2,GV,GF,IR,VBD,AGV,VBM,PP,PPT,GVn,WGF,VBp,VBm,ST,AT,HT,T1,T2,T3,F1,F2,F3,F4,F5,PSL,RRB,VPm;
    char note[3];

    if((VB = GetDSPdata(fdser,yAddress,opGetDSP,ToM5,_opGetDSP)) < 0) return(-1);
    if((ILD = GetDSPdata(fdser,yAddress,opGetDSP,ToM6,_opGetDSP)) < 0) return(-1);
    if((ILI = GetDSPdata(fdser,yAddress,opGetDSP,ToM7,_opGetDSP)) < 0) return(-1);
    if((P1 = GetDSPdata(fdser,yAddress,opGetDSP,ToM8,_opGetDSP)) < 0) return(-1);
    if((P2 = GetDSPdata(fdser,yAddress,opGetDSP,ToM9,_opGetDSP)) < 0) return(-1);
    if((GV = GetDSPdata(fdser,yAddress,opGetDSP,ToM28,_opGetDSP)) < 0) return(-1);
    if((GF = GetDSPdata(fdser,yAddress,opGetDSP,ToM29,_opGetDSP)) < 0) return(-1);
    if((IR = GetDSPdata(fdser,yAddress,opGetDSP,ToM30,_opGetDSP)) < 0) return(-1);
    if((VBD = GetDSPdata(fdser,yAddress,opGetDSP,ToM31,_opGetDSP)) < 0) return(-1);
    if((AGV = GetDSPdata(fdser,yAddress,opGetDSP,ToM32,_opGetDSP)) < 0) return(-1);
    if((VBM = GetDSPdata(fdser,yAddress,opGetDSP,ToM33,_opGetDSP)) < 0) return(-1);
    if((PP = GetDSPdata(fdser,yAddress,opGetDSP,ToM34,_opGetDSP)) < 0) return(-1);
    if((PPT = GetDSPdata(fdser,yAddress,opGetDSP,ToM35,_opGetDSP)) < 0) return(-1);
    if((GVn = GetDSPdata(fdser,yAddress,opGetDSP,ToM36,_opGetDSP)) < 0) return(-1);
    if((WGF = GetDSPdata(fdser,yAddress,opGetDSP,ToM37,_opGetDSP)) < 0) return(-1);
    if((VBp = GetDSPdata(fdser,yAddress,opGetDSP,ToM45,_opGetDSP)) < 0) return(-1);
    if((VBm = GetDSPdata(fdser,yAddress,opGetDSP,ToM46,_opGetDSP)) < 0) return(-1);
    if((ST = GetDSPdata(fdser,yAddress,opGetDSP,ToM47,_opGetDSP)) < 0) return(-1);
    if((AT = GetDSPdata(fdser,yAddress,opGetDSP,ToM48,_opGetDSP)) < 0) return(-1);
    if((HT = GetDSPdata(fdser,yAddress,opGetDSP,ToM49,_opGetDSP)) < 0) return(-1);
    if((T1 = GetDSPdata(fdser,yAddress,opGetDSP,ToM50,_opGetDSP)) < 0) return(-1);
    if((T2 = GetDSPdata(fdser,yAddress,opGetDSP,ToM51,_opGetDSP)) < 0) return(-1);
    if((T3 = GetDSPdata(fdser,yAddress,opGetDSP,ToM52,_opGetDSP)) < 0) return(-1);
    if((F1 = GetDSPdata(fdser,yAddress,opGetDSP,ToM52,_opGetDSP)) < 0) return(-1);
    if((F2 = GetDSPdata(fdser,yAddress,opGetDSP,ToM54,_opGetDSP)) < 0) return(-1);
    if((F3 = GetDSPdata(fdser,yAddress,opGetDSP,ToM55,_opGetDSP)) < 0) return(-1);
    if((F4 = GetDSPdata(fdser,yAddress,opGetDSP,ToM56,_opGetDSP)) < 0) return(-1);
    if((F5 = GetDSPdata(fdser,yAddress,opGetDSP,ToM57,_opGetDSP)) < 0) return(-1);
    if((PSL = GetDSPdata(fdser,yAddress,opGetDSP,ToM58,_opGetDSP)) < 0) return(-1);
    if((RRB = GetDSPdata(fdser,yAddress,opGetDSP,ToM59,_opGetDSP)) < 0) return(-1);
    if((VPm = GetDSPdata(fdser,yAddress,opGetDSP,ToM60,_opGetDSP)) < 0) return(-1);

    note[0] = '\0';
    if(bColumns) {
        if (AGV == VBD) strcpy(note,"*\0");
        fprintf(outfp, "%11.6f  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f  %11.6f%s  %11.6f%s  %11.6f  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f  %11.6f  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  ",VB,VBM,note,VBp,note,VBm,note,VBD,ILD,ILI,IR,GV,AGV,note,GVn,note,GF,PP,note,PPT,note,ST,note,AT,note,HT,note,T1,note,T2,note,T3,note,F1,note,F2,note,F3,note,F4,note,F5,note,P1,P2,PSL,note,RRB,note,VPm,note,WGF,note);
        bColOutput = TRUE;
    }
    else
    {
        if (AGV == VBD) strcpy(note," *\0");
        fprintf(outfp, "\nExtended DSP Reporting\n");
        fprintf(outfp, "%-31s = %11.6f V\n",_ToM5,VB);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM33,VBM,note);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM45,VBp,note);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM46,VBm,note);
        fprintf(outfp, "%-31s = %11.6f V\n",_ToM31,VBD);
        fprintf(outfp, "\n%-31s = %11.6f A\n",_ToM6,ILD);
        fprintf(outfp, "%-31s = %11.6f A\n",_ToM7,ILI);
        fprintf(outfp, "%-31s = %11.6f Mohm\n",_ToM30,IR);
        fprintf(outfp, "\n%-31s = %11.6f V\n",_ToM28,GV);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM32,AGV,note);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM36,GVn,note);
        fprintf(outfp, "%-31s = %11.6f Hz\n",_ToM29,GF);
        fprintf(outfp, "\n%-31s = %11.6f W  %s\n",_ToM34,PP,note);
        fprintf(outfp, "%-31s = %11.6f W  %s\n",_ToM35,PPT,note);
        fprintf(outfp, "\n%-31s = %11.6f C  %s\n",_ToM47,ST,note);
        fprintf(outfp, "%-31s = %11.6f C  %s\n",_ToM48,AT,note);
        fprintf(outfp, "%-31s = %11.6f C  %s\n",_ToM49,HT,note);
        fprintf(outfp, "%-31s = %11.6f C  %s\n",_ToM50,T1,note);
        fprintf(outfp, "%-31s = %11.6f C  %s\n",_ToM51,T2,note);
        fprintf(outfp, "%-31s = %11.6f C  %s\n",_ToM52,T3,note);
        fprintf(outfp, "\n%-31s = %11.6f RPM%s\n",_ToM53,F1,note);
        fprintf(outfp, "%-31s = %11.6f RPM%s\n",_ToM54,F2,note);
        fprintf(outfp, "%-31s = %11.6f RPM%s\n",_ToM55,F3,note);
        fprintf(outfp, "%-31s = %11.6f RPM%s\n",_ToM56,F4,note);
        fprintf(outfp, "%-31s = %11.6f RPM%s\n",_ToM57,F5,note);
        fprintf(outfp, "\n%-31s = %11.6f W\n",_ToM8,P1);
        fprintf(outfp, "%-31s = %11.6f W\n",_ToM9,P2);
        fprintf(outfp, "%-31s = %11.6f W  %s\n",_ToM58,PSL,note);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM59,RRB,note);
        fprintf(outfp, "%-31s = %11.6f V  %s\n",_ToM60,VPm,note);
        fprintf(outfp, "%-31s = %11.6f Hz %s\n",_ToM37,WGF,note);
        if (AGV == VBD) fprintf(outfp, "(Note: * = May not be in this Inverter's firmware)\n");
    }

    return(0);
}

/*--------------------------------------------------------------------------
    GetDSP3Phase
----------------------------------------------------------------------------*/
int GetDSP3Phase(int fdser, int yAddress)
{
    float VBD,GVPn,GVPr,GVPs,GVPt,GCPr,GCPs,GCPt,FRQPr,FRQPs,FRQPt;
    char note[3];

    if((VBD = GetDSPdata(fdser,yAddress,opGetDSP,ToM31,_opGetDSP)) < 0) return(-1);
    if((GVPn = GetDSPdata(fdser,yAddress,opGetDSP,ToM38,_opGetDSP)) < 0) return(-1);
    if((GVPr = GetDSPdata(fdser,yAddress,opGetDSP,ToM61,_opGetDSP)) < 0) return(-1);
    if((GVPs = GetDSPdata(fdser,yAddress,opGetDSP,ToM62,_opGetDSP)) < 0) return(-1);
    if((GVPt = GetDSPdata(fdser,yAddress,opGetDSP,ToM63,_opGetDSP)) < 0) return(-1);
    if((GCPr = GetDSPdata(fdser,yAddress,opGetDSP,ToM39,_opGetDSP)) < 0) return(-1);
    if((GCPs = GetDSPdata(fdser,yAddress,opGetDSP,ToM40,_opGetDSP)) < 0) return(-1);
    if((GCPt = GetDSPdata(fdser,yAddress,opGetDSP,ToM41,_opGetDSP)) < 0) return(-1);
    if((FRQPr = GetDSPdata(fdser,yAddress,opGetDSP,ToM42,_opGetDSP)) < 0) return(-1);
    if((FRQPs = GetDSPdata(fdser,yAddress,opGetDSP,ToM43,_opGetDSP)) < 0) return(-1);
    if((FRQPt = GetDSPdata(fdser,yAddress,opGetDSP,ToM44,_opGetDSP)) < 0) return(-1);

    note[0] = '\0';
    if(bColumns) {
        if (GVPn == VBD) strcpy(note,"*\0");
        fprintf(outfp, "%11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  %11.6f%s  ",GVPn,note,GVPr,note,GVPs,note,GVPt,note,GCPr,note,GCPs,note,GCPt,note,FRQPr,note,FRQPs,note,FRQPt,note);
        bColOutput = TRUE;
    }
    else
    {
        if (GVPn == VBD) strcpy(note," *\0");
        fprintf(outfp, "\n3-Phase DSP Reporting\n");
        fprintf(outfp, "%-26s = %11.6f V %s\n",_ToM38,GVPn,note);
        fprintf(outfp, "%-26s = %11.6f V %s\n",_ToM61,GVPr,note);
        fprintf(outfp, "%-26s = %11.6f V %s\n",_ToM62,GVPs,note);
        fprintf(outfp, "%-26s = %11.6f V %s\n",_ToM63,GVPt,note);
        fprintf(outfp, "\n%-26s = %11.6f A %s\n",_ToM39,GCPr,note);
        fprintf(outfp, "%-26s = %11.6f A %s\n",_ToM40,GCPs,note);
        fprintf(outfp, "%-26s = %11.6f A %s\n",_ToM41,GCPt,note);
        fprintf(outfp, "\n%-26s = %11.6f Hz%s\n",_ToM42,FRQPr,note);
        fprintf(outfp, "%-26s = %11.6f Hz%s\n",_ToM43,FRQPs,note);
        fprintf(outfp, "%-26s = %11.6f Hz%s\n",_ToM44,FRQPt,note);
        if (GVPn == VBD) fprintf(outfp, "(Note: * = May not be in this Inverter's firmware)\n");
    }

    return(0);
}

/*--------------------------------------------------------------------------
    GetDSPdata
----------------------------------------------------------------------------*/
float GetDSPdata(int fdser, int yAddress, int opcode, int param, char desription[])
{
    int nCnt;
    float *paramValFloat;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opcode;		/* set Measure request to the DSP opcode */
    szSerBuffer[cParam1] = param;
    szSerBuffer[cCommandEnd+1] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],desription)) {

        paramValFloat = cvrtFloat(szSerBuffer);
        if(bVerbose) fprintf(stderr, "value     %12.6f\n",*paramValFloat);
    }
    else
        return(-1);

    return(*paramValFloat);
}


/*--------------------------------------------------------------------------
    GetJoules
----------------------------------------------------------------------------*/
int GetJoules(int fdser, int yAddress)
{
    int nCnt;
    unsigned long Joules;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetEnergy10Sec;
    szSerBuffer[cCommandEnd] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetEnergy10Sec)) {
        Joules = cvrtShort(szSerBuffer);
        if(bVerbose) fprintf(stderr, "Joules       %12lu\n",(unsigned long)Joules);
        fprintf(outfp, "\nEnergy in the last 10 seconds (Joules) : %11.6f\n",(unsigned long)Joules*0.319509);
        return(0);
   }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetTime
----------------------------------------------------------------------------*/
int GetTime(int fdser, int yAddress)
{
    int nCnt = 0;
    time_t timeValLong;
    struct tm tim;
    char curTime[24];
    char fromInv[10] = "Inverter \0";
    BOOL DLS = TRUE;

    if(bGetInvTime) {
        strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
        szSerBuffer[cAddress] = yAddress;	/* set inverter address */
        szSerBuffer[cCommand] = opGetTime;
        szSerBuffer[cCommandEnd] = '\0';
        nCnt = Communicate(fdser, yAddress);
    }
    if (nCnt > 0) {
        if (! TransState((int)szSerBuffer[aState],_opGetTime)) return(-1);
        if(bVerbose) {
            timeValLong = (time_t)TimeBase;
            fprintf(stderr, "\nTimeBase     %12lu\n",(time_t)TimeBase);
            fprintf(stderr, "Base Inverter date/time: %s\n",ctime(&timeValLong));
        }
        timeValLong = (time_t)cvrtLong(szSerBuffer);
        if(bVerbose) fprintf(stderr, "timeValLong  %12lu\n",(time_t)timeValLong);
        timeValLong += (time_t)TimeBase;
        if(DLS) timeValLong -= (time_t)(60*60);		/* adjust for daylight savings time */
        if(bVerbose) fprintf(stderr, "timeValLong  %12lu\n",(time_t)timeValLong);
    }
    if(bGetLocTime) {
        timeValLong = time(NULL);
        fromInv[0] = '\0';
    }
    if (nCnt > 0 || bGetLocTime) {
        tim = *(localtime(&timeValLong));
        if(!bColumns || (!bGetDSP && !bGetEnergy)) {
            fprintf(outfp, "\nCurrent %sdate/time: ",fromInv);
            strftime(curTime,24,"%d-%b-%Y %H:%M:%S\n",&tim);
        }
        else {
            strftime(curTime,24,"%Y%m%d-%H:%M:%S ",&tim);
            bColOutput = TRUE;
        }
        fprintf(outfp, "%s",curTime);
        return(0);
   }
    return(-1);
}


/*--------------------------------------------------------------------------
    SetTime
----------------------------------------------------------------------------*/
int SetTime(int fdser, int yAddress)
{
    int nCnt = 0;
    int rc;
    time_t timeValLong;
    struct tm tim;
    char curTime[24];
    char answer;

    printf("\n**** WARNING *****   ***** WARNING *****   ***** WARNING ****\n");
    printf("Setting the Date and Time has been known to clear all History\n");
    printf("               (Not enabled on all models.)\n");
    printf("\nAre you sure your want to proceed? y/[n] : ");
    rc = scanf("%c", &answer);
    if(answer != 'y' && answer != 'Y') return(1);

    timeValLong = time((time_t *) NULL);
    if(bVerbose) {
	fprintf(stderr, "\ntimeValLong        %12lu\n",(time_t)timeValLong);
        tim = *(localtime(&timeValLong));
        strftime(curTime,24,"%d-%b-%Y %H:%M:%S",&tim);
        fprintf(stderr, "setting time to %s DST %i\n",curTime,tim.tm_isdst);
    }

    /* adjust time to Aurora's time base */
    timeValLong -= (long)TimeBase;
    if(bVerbose) fprintf(stderr, "timeValLong        %12lu\n",(time_t)timeValLong);

    /* adjust for Daylight Saving Time */
    if (tim.tm_isdst == 1) {
        timeValLong += 3600;
        if(bVerbose) fprintf(stderr, "timeValLong (DST)  %12lu\n",(time_t)timeValLong);
    }

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opSetTime;
    szSerBuffer[cParam1] = (timeValLong >> 24) & 0xff;
    szSerBuffer[cParam2] = (timeValLong >> 16) & 0xff;
    szSerBuffer[cParam3] = (timeValLong >> 8) & 0xff;
    szSerBuffer[cParam4] = timeValLong & 0xff;
    szSerBuffer[cCommandEnd+4] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0) {
        if (! TransState((int)szSerBuffer[aState],_opSetTime)) return(-1);
        printf("\nInverter date/time set.");
        return(0);
   }
    return(-1);
}


/*--------------------------------------------------------------------------
    GetCounters
----------------------------------------------------------------------------*/
int GetCounters(int fdser, int yAddress)
{
    char TRT[18], PRT[18], TGC[18], RPC[18];

    if(GetCountersData(fdser, yAddress, cTotalRun, TRT) < 0) return(-1);
    if(GetCountersData(fdser, yAddress, cPartialRun, PRT) < 0) return(-1);
    if(GetCountersData(fdser, yAddress, cTotalGrid, TGC) < 0) return(-1);
    if(GetCountersData(fdser, yAddress, cResetPartial, RPC) < 0) return(-1);

    fprintf(outfp, "\n%-34s   %18s\n","","yyy ddd hh:mm:ss");
    fprintf(outfp, "%-34s : %18s\n",_cTotalRun,TRT);
    fprintf(outfp, "%-34s : %18s\n",_cPartialRun,PRT);
    fprintf(outfp, "%-34s : %18s\n",_cTotalGrid,TGC);
    fprintf(outfp, "%-34s : %18s\n",_cResetPartial,RPC);

    return(0);
}


/*--------------------------------------------------------------------------
    GetCountersData
----------------------------------------------------------------------------*/
int GetCountersData(int fdser, int yAddress, int param, char *uptime)
{
    int nCnt;
    unsigned long paramValPtr, paramValLong;
    int years, days, hours, minutes, seconds;

    strcpy(szSerBuffer, _clearCMD);		/* clear Aurora cmd string */
    szSerBuffer[cAddress] = yAddress;		/* set inverter address */
    szSerBuffer[cCommand] = opGetCounters;	/* set Measure request to the DSP opcode */
    szSerBuffer[cParam1] = param;
    szSerBuffer[cCommandEnd+1] = '\0';
    nCnt = Communicate(fdser, yAddress);
    if (nCnt > 0 && TransState((int)szSerBuffer[aState],_opGetCounters)) {
        paramValPtr = cvrtLong(szSerBuffer);
        if(bVerbose) fprintf(stderr, "value        %12lu\n",(unsigned long)paramValPtr);
    }
    else
        return(-1);

    paramValLong = (unsigned long)paramValPtr;
    if(bVerbose) fprintf(stderr, "paramValLong %12lu\n",(unsigned long)paramValLong);
    years = paramValLong / SecsInYear;
    paramValLong -= (years * SecsInYear);
    if(bVerbose) fprintf(stderr, "paramValLong %12li\tyears   %3i\n",(unsigned long)paramValLong,years);
    days = paramValLong / SecsInDay;
    paramValLong -= (days * SecsInDay);
    if(bVerbose) fprintf(stderr, "paramValLong %12li\tdays    %3i\n",(unsigned long)paramValLong,days);
    hours = paramValLong / SecsInHour;
    paramValLong -= (hours * SecsInHour);
    if(bVerbose) fprintf(stderr, "paramValLong %12li\thours   %3i\n",(unsigned long)paramValLong,hours);
    minutes = paramValLong / SecsInMinute;
    paramValLong -= (minutes * SecsInMinute);
    if(bVerbose) fprintf(stderr, "paramValLong %12li\tminutes %3i\n",(unsigned long)paramValLong,minutes);
    seconds = paramValLong;
    if(bVerbose) fprintf(stderr, "paramValLong %12li\tseconds %3i\n",(unsigned long)paramValLong,seconds);
    
    sprintf(uptime,"%3d %3d %02d:%02d:%02d",years,days,hours,minutes,seconds);
    if(bVerbose) fprintf(stderr, "uptime: %s\n",uptime);

    return(0);
}


/*--------------------------------------------------------------------------
    Communicate
----------------------------------------------------------------------------*/
int Communicate(int fdser, int yAddress)
{
    int i = 0;
    int rc, nCnt;
    char ch;
    char szSerBufferSave[_szSerBufferLen];
    int CRCrc = -1;
    int attempts = 1;
    struct timeval curtv;
    long long int curtvusecs = 0, lastcommtvusecs = 0, elapsedtvusecs = 0;

    strcpy(szSerBufferSave, _clearCMD);
    strcpy(szSerBufferSave, szSerBuffer);
    gettimeofday(&curtv, NULL);
    while(CRCrc < 0 && attempts <= yMaxAttempts) {
        if(lastcommtv.tv_sec == 0 || lastcommtv.tv_usec == 0) {
            lastcommtv.tv_sec = curtv.tv_sec;
            lastcommtv.tv_usec = curtv.tv_usec;
        } else {
            curtvusecs = (curtv.tv_sec*1000000) + curtv.tv_usec;
            lastcommtvusecs = (lastcommtv.tv_sec*1000000) + lastcommtv.tv_usec;
            elapsedtvusecs = curtvusecs - lastcommtvusecs;
        }
        strcpy(szSerBuffer, szSerBufferSave);
        if(bVerbose) fprintf(stderr, "\nElapsed time since last comm %llu uS\nAttempt %i",elapsedtvusecs,attempts);
        if(yCommPause > 0 && elapsedtvusecs < yCommPause) {
            if(bVerbose) fprintf(stderr, " Sleeping for %llu uS",yCommPause-elapsedtvusecs);
            usleep(yCommPause-elapsedtvusecs);
        }
        if(bVerbose) fprintf(stderr, "\nClearing read buffer  ");
        while((rc = ReadNextChar(fdser, &ch, 0)) && i < _clrAttemps) {
            if(bVerbose) fprintf(stderr, ":ch=%i ",ch);		/* clear channel and delay */
            i++;
        }
        if(bVerbose) {
            fprintf(stderr, ":ch=%i ",ch);
            if (rc != 0 && i >= _clrAttemps)
                fprintf(stderr, "max attempts reached (%i)\n",i);
            else
                fprintf(stderr, "Success!\n");
        }
        if(bVerbose) {
            fprintf(stderr, "szSerBufferSave ");
            if (strcmp(szSerBuffer,szSerBufferSave) == 0)
                fprintf(stderr, "OK! ");
            else
                fprintf(stderr, "ERROR! ");
            for (i = 0; i < cSIZE; i++) {
                fprintf(stderr, "%02x ",(unsigned char)szSerBufferSave[i]);
            }
            fprintf(stderr, "\n");
        }
        crcValue = crc16(szSerBuffer, 8);
        szSerBuffer[cCRC_L] = LOBYTE(crcValue);
        szSerBuffer[cCRC_H] = HIBYTE(crcValue);
        szSerBuffer[cEND] = '\0';
        if(bVerbose) {
            fprintf(stderr, "command: ");
            for (i = 0; i < cSIZE; i++) {
                fprintf(stderr, "%02x ",(unsigned char)szSerBuffer[i]);
            }
            fprintf(stderr, "\nFlushing serial device buffer... ");
        }
        errno = 0;
        if (tcflush(fdser,TCIOFLUSH))
            fprintf(stderr, "Problem flushing before sending command: (%i) %s\n",errno,strerror (errno));
        else
            if(bVerbose) fprintf(stderr, "Success!\nSending command... ");
        nCnt = write(fdser, &szSerBuffer, cSIZE);		/* send it */
        if(bVerbose) fprintf(stderr, "sent %d characters\nDraining serial device buffer... ", nCnt);
        errno = 0;
        if (tcdrain(fdser))
            fprintf(stderr, "Problem draining command: (%i) %s\n",errno,strerror (errno));
        else
            if(bVerbose) fprintf(stderr, "Success!\n");
        if(szSerBuffer[cCommand] == opSetTime) Delay(1, 0L);
        strcpy(szSerBuffer, _clearCMD);
        if(bVerbose) {
            fprintf(stderr, "Cleared data buffer: ");
            for (i = 0; i < cSIZE; i++) {
                fprintf(stderr, "%02x ",(unsigned char)szSerBuffer[i]);
            }
            fprintf(stderr, "\n");
        }
        if (yReadPause > 0 ) {
            if(bVerbose)
                fprintf(stderr, "Waiting %d milli-seconds before reading inverter response\n",yReadPause);
            else
                if (bRptReadPause) fprintf(stderr, "\n%s: %s: Waiting %d milli-seconds before reading inverter response",RunTime,ProgramName,yReadPause);
            usleep(yReadPause*1000);
        }
        tcflush(fdser,TCIFLUSH);
        nCnt = ReadToBuffer(fdser, szSerBuffer, aSIZE);
        if(bVerbose) {
            fprintf(stderr, "answer:  ");
            if (nCnt > 0) {
                for (i = 0; i < nCnt; i++) {
                    fprintf(stderr, "%02x ",(unsigned char)szSerBuffer[i]);
                }
                fprintf(stderr, "\nreceived %d characters\n", nCnt);
            } else
                fprintf(stderr, "Got %d characters\n", nCnt);
        }
        if (nCnt > 0) {
            crcValue = crc16(szSerBuffer, 6);
            if((unsigned char)szSerBuffer[aCRC_L] != LOBYTE(crcValue) || (unsigned char)szSerBuffer[aCRC_H] != HIBYTE(crcValue)) {
                if (yMaxAttempts == 1 || attempts == yMaxAttempts)
                    if(!bCommCheck) {
                        if (! bVerbose && bRptReadPause) fprintf(stderr, "\n");
                        fprintf(stderr, "%s: CRC receive error (%i attempts made) %04x %02x %02x\n",RunTime,attempts,crcValue,(unsigned char)szSerBuffer[aCRC_H],(unsigned char)szSerBuffer[aCRC_L]);
                    }
            } else {
                if(bVerbose) fprintf(stderr, "CRC receive OK %04x\n",crcValue);
                CRCrc = 0;
            }
        }
        gettimeofday(&curtv, NULL);
        lastcommtv.tv_sec = curtv.tv_sec;
        lastcommtv.tv_usec = curtv.tv_usec;
        attempts++;
    }
    if (CRCrc < 0) return(-1);
    if (bRptReties) {
        fprintf(stderr, "\n%s: %s: %i attempts made",RunTime,ProgramName,attempts-1);
        if(bVerbose) fprintf(stderr, "\n");
    } else
        if (bRptReadPause) fprintf(stderr, "\n");
    return(nCnt);
}

/*--------------------------------------------------------------------------
    cvrtFloat
    Converts a 4 char string to a float.
----------------------------------------------------------------------------*/
float *cvrtFloat(char *Buffer)
{
    unsigned char cValue[4];
    float *value;

    cValue[0] = Buffer[aParam4];
    cValue[1] = Buffer[aParam3];
    cValue[2] = Buffer[aParam2];
    cValue[3] = Buffer[aParam1];

    value = (float *)cValue;
    if(bVerbose) fprintf(stderr, "cvrtFloat %12.6f\n",*value);

    return(value);
}

/*--------------------------------------------------------------------------
    cvrtLong
    Converts a 4 char string to a long.
----------------------------------------------------------------------------*/
unsigned long cvrtLong(char *Buffer)
{
    unsigned char cValue[4];
    unsigned long *value;

    cValue[0] = Buffer[aParam4] & 0xff;
    cValue[1] = Buffer[aParam3] & 0xff;
    cValue[2] = Buffer[aParam2] & 0xff;
    cValue[3] = Buffer[aParam1] & 0xff;

    value = (unsigned long *)cValue;
    if(bVerbose) fprintf(stderr, "cvrtLong     %12lu\n",*value);

    return(*value);
}


/*--------------------------------------------------------------------------
    cvrtShort
    Converts a 2 char string to a long.
----------------------------------------------------------------------------*/
unsigned long cvrtShort(char *Buffer)
{
    unsigned char cValue[4];
    unsigned long *value;

    cValue[0] = Buffer[aParam2] & 0xff;
    cValue[1] = Buffer[aParam1] & 0xff;

    value = (unsigned long *)cValue;
    if(bVerbose) fprintf(stderr, "cvrtShort    %12lu\n",*value);

    return(*value);
}


/*--------------------------------------------------------------------------
    FindString
    Reads command line parameters.
----------------------------------------------------------------------------*/
char* FindString(WORD wRule, char *ptr)
{

    while(wRule--) {		/* walk thru the null terminators */
        while(*ptr++)
        ;
    }
    return ptr;
}
 

/*--------------------------------------------------------------------------
    ReadNextChar
    Reads the next character from the serial device. Returns zero if no
    character was available.
----------------------------------------------------------------------------*/
int ReadNextChar(int nfd, char *pChar, int timeout)
{
    int nResult = -1;
    long int eUsecs, sSecs, cSecs, sUsecs, cUsecs;
    struct timeval tv;

    errno = 0;

    sUsecs = cUsecs = 0;
    eUsecs = sSecs = cSecs = sUsecs = cUsecs = 0;
    memset (pChar, 0, sizeof (*pChar));
    if (timeout > 0) {
        eUsecs = 0;
        gettimeofday(&tv, NULL);
        sSecs = tv.tv_sec;
        sUsecs = tv.tv_usec;
        while (nResult <= 0 && errno == 0 && eUsecs <= timeout) {
            nResult = read(nfd, pChar, 1);
            gettimeofday(&tv, NULL);
            cSecs = tv.tv_sec;
            cUsecs = tv.tv_usec;
            eUsecs = ((cSecs-sSecs)*1000000) + (cUsecs-sUsecs);
        }
    } else
        nResult = read(nfd, pChar, 1);
    if (errno != 0)
        fprintf (stderr, "\naurora: (TO) Problem reading serial device, (nResult %i) (errno %i) %s.\n",nResult,errno,strerror (errno));
    if(nResult == -1) {
        if (errno == 0) perror("\naurora: (TO) Problem reading serial device. \n");
        fprintf (stderr, "\n");
        RestorePort(nfd);
        ClrSerLock(PID);
        fprintf (stderr, "\n");
        exit(2);
    }
    if(bVerbose) fprintf(stderr, "RC=%i (%02x)",nResult,(unsigned char)*pChar);
    if(bVerbose && timeout > 0 && (cUsecs-sUsecs) > 0) fprintf(stderr, " Read waited %i uS.",(int)(cUsecs-sUsecs));
    return nResult;

}

/*--------------------------------------------------------------------------
    ReadToBuffer
    Reads data to a buffer until no more characters are available. If
    the buffer overflows, returns -1. Otherwise, returns the number of
    characters read.
----------------------------------------------------------------------------*/
int ReadToBuffer(int nfd, char *pszBuffer, int nBufSize)
{
    int nPos = 0;		/* current character position */
    int attempts = 0;
    char *pBuf = pszBuffer;
    int rc = 0;
    int sanity = 0;

    while(attempts < nBufSize && sanity < 100) {
        if(bVerbose) fprintf(stderr, "read attempts %i timeout %iuS ",attempts+1,yTimeout);
        rc = ReadNextChar(nfd, pBuf++, yTimeout);
        if(rc < 0)
            return nPos;	/* no character available */
        if (rc > 0) ++nPos;
        ++attempts;
        sanity++;
        if(bVerbose) fprintf(stderr, "\n");
    }
    if (rc < 0) return -1;	/* problem */
    return nPos;
}


/*--------------------------------------------------------------------------
    crc16
                                         16   12   5
    this is the CCITT CRC 16 polynomial X  + X  + X  + 1.
    This is 0x1021 when x is 2, but the way the algorithm works
    we use 0x8408 (the reverse of the bit pattern).  The high
    bit is always assumed to be set, thus we only use 16 bits to
    represent the 17 bit value.
----------------------------------------------------------------------------*/

#define POLY 0x8408   /* 1021H bit reversed */

WORD crc16(char *data_p, unsigned short length)
{
      unsigned char i;
      unsigned int data;
      unsigned int crc = 0xffff;

      if (length == 0)
        return (~crc);
      do
      {
        for (i=0, data=(unsigned int)0xff & *data_p++;
         i < 8; 
         i++, data >>= 1)
        {
          if ((crc & 0x0001) ^ (data & 0x0001))
            crc = (crc >> 1) ^ POLY;
          else  crc >>= 1;
        }
      } while (--length);

      crc = ~crc;

      return (crc);
}

/*--------------------------------------------------------------------------
    Delay
    Delays by the number of seconds and microseconds.
----------------------------------------------------------------------------*/
void Delay(int secs, long microsecs)
{
    static struct timeval t1;

    t1.tv_sec = (long)secs;
    t1.tv_usec = microsecs;
    if ( select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &t1)  < 0 )
         perror("Internal error: error in select()");
    return;
}


